Defensive Programming Vs Fail Fast Exercises
Defensive Programming vs Fail-Fast - Practice Exercises
Comprehensive exercises to master when to use defensive programming vs fail-fast approaches.
---
Foundational Understanding
Q: Explain the difference between defensive programming and fail-fast. When should each be used?
A: Defensive programming anticipates and handles invalid input gracefully to keep the system running. Use it at system boundaries (APIs, external integrations, user input).
Fail-fast detects invalid states early and throws exceptions immediately. Use it in core business logic to enforce invariants and surface bugs early.
Key principle: Defensive at the edges, fail-fast at the core.
// Defensive: API boundary
[HttpPost]
public IActionResult CreateOrder([FromBody] OrderRequest request)
{
if (request == null || request.Quantity <= 0)
return BadRequest("Invalid request");
// Call domain logic...
}
// Fail-Fast: Domain entity
public class Order
{
public Order(decimal quantity)
{
if (quantity <= 0)
throw new ArgumentException("Quantity must be positive");
Quantity = quantity;
}
}
Q: What are the risks of being overly defensive in core business logic?
A: Overly defensive code in the core can:
- Hide bugs by silently ignoring invalid states
- Make debugging difficult due to silent failures
- Allow invalid data to propagate through the system
- Create false sense of security
- Violate fail-fast principle that catches errors early
// ❌ Bad: Too defensive in domain logic
public class PriceCalculator
{
public decimal Calculate(decimal price, decimal quantity)
{
// Silently returning 0 hides programming errors
if (price <= 0 || quantity <= 0)
return 0;
return price * quantity;
}
}
// ✅ Good: Fail-fast exposes the bug
public class PriceCalculator
{
public decimal Calculate(decimal price, decimal quantity)
{
if (price <= 0)
throw new ArgumentException("Price must be positive");
if (quantity <= 0)
throw new ArgumentException("Quantity must be positive");
return price * quantity;
}
}
Q: How do you handle exceptions from fail-fast code at the API layer?
A: Catch domain exceptions at the API layer and translate them into appropriate HTTP responses.
[HttpPost("orders")]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
try
{
// Domain code may throw (fail-fast)
var order = new Order(request.Symbol, request.Quantity, request.Price);
await _orderService.CreateAsync(order);
return Ok(order);
}
catch (ArgumentException ex)
{
// Translate to user-friendly error
return BadRequest(new { error = ex.Message });
}
catch (RiskLimitExceededException ex)
{
return BadRequest(new { error = ex.Message, code = "RISK_LIMIT_EXCEEDED" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error creating order");
return StatusCode(500, "An unexpected error occurred");
}
}
---
Intermediate Scenarios
Q: Implement a defensive wrapper around an unreliable external price feed API.
A: Create a defensive adapter that handles failures gracefully with fallback mechanisms.
public class ResilientPriceFeedAdapter
{
private readonly IPriceFeedClient _primaryFeed;
private readonly IPriceFeedClient _fallbackFeed;
private readonly IMemoryCache _cache;
private readonly ILogger _logger;
public async Task<decimal?> GetPriceAsync(string symbol)
{
// Defensive: validate input
if (string.IsNullOrWhiteSpace(symbol))
{
_logger.LogWarning("Empty symbol provided");
return null;
}
// Try cache first
if (_cache.TryGetValue($"price:{symbol}", out decimal cachedPrice))
{
return cachedPrice;
}
// Try primary feed
try
{
var price = await _primaryFeed.GetPriceAsync(symbol);
// Defensive: validate response
if (price.HasValue && price.Value > 0)
{
_cache.Set($"price:{symbol}", price.Value, TimeSpan.FromSeconds(30));
return price;
}
_logger.LogWarning("Invalid price from primary feed: {Price}", price);
}
catch (HttpRequestException ex)
{
_logger.LogWarning(ex, "Primary feed failed for {Symbol}", symbol);
}
catch (TimeoutException ex)
{
_logger.LogWarning(ex, "Primary feed timeout for {Symbol}", symbol);
}
// Try fallback feed
try
{
var price = await _fallbackFeed.GetPriceAsync(symbol);
if (price.HasValue && price.Value > 0)
{
_cache.Set($"price:{symbol}", price.Value, TimeSpan.FromSeconds(30));
return price;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Fallback feed failed for {Symbol}", symbol);
}
// All sources failed - return null to indicate unavailability
return null;
}
}
Q: Create a fail-fast domain entity for a trading Order with strict invariant enforcement.
A: Implement an Order aggregate root that fails fast on any invariant violation.
public class Order
{
public Guid Id { get; private set; }
public string Symbol { get; private set; }
public decimal Quantity { get; private set; }
public decimal Price { get; private set; }
public OrderSide Side { get; private set; }
public OrderStatus Status { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime? ExecutedAt { get; private set; }
public Order(string symbol, decimal quantity, decimal price, OrderSide side)
{
// Fail-fast: enforce construction invariants
if (string.IsNullOrWhiteSpace(symbol))
throw new ArgumentException("Symbol is required", nameof(symbol));
if (symbol.Length > 20)
throw new ArgumentException("Symbol too long (max 20 characters)", nameof(symbol));
if (quantity <= 0)
throw new ArgumentException("Quantity must be positive", nameof(quantity));
if (quantity > 1_000_000)
throw new ArgumentException("Quantity exceeds maximum allowed", nameof(quantity));
if (price < 0)
throw new ArgumentException("Price cannot be negative", nameof(price));
if (price > 1_000_000)
throw new ArgumentException("Price exceeds maximum allowed", nameof(price));
Id = Guid.NewGuid();
Symbol = symbol.ToUpperInvariant();
Quantity = quantity;
Price = price;
Side = side;
Status = OrderStatus.Pending;
CreatedAt = DateTime.UtcNow;
}
public void Execute(decimal executedPrice, DateTime executedAt)
{
// Fail-fast: enforce state transitions
if (Status != OrderStatus.Pending)
throw new InvalidOperationException(
$"Cannot execute order in {Status} status. Only Pending orders can be executed.");
if (executedPrice <= 0)
throw new ArgumentException("Executed price must be positive", nameof(executedPrice));
if (executedAt < CreatedAt)
throw new ArgumentException("Execution time cannot be before creation time", nameof(executedAt));
if (executedAt > DateTime.UtcNow.AddMinutes(1))
throw new ArgumentException("Execution time cannot be in the future", nameof(executedAt));
Price = executedPrice;
ExecutedAt = executedAt;
Status = OrderStatus.Executed;
}
public void Cancel(string reason)
{
if (string.IsNullOrWhiteSpace(reason))
throw new ArgumentException("Cancellation reason is required", nameof(reason));
if (Status == OrderStatus.Executed)
throw new InvalidOperationException("Cannot cancel executed order");
if (Status == OrderStatus.Cancelled)
throw new InvalidOperationException("Order is already cancelled");
Status = OrderStatus.Cancelled;
}
}
public enum OrderStatus { Pending, Executed, Cancelled }
public enum OrderSide { Buy, Sell }
Q: Design a multi-layer validation strategy combining defensive and fail-fast approaches.
A: Implement validation at multiple layers with appropriate strategy for each.
// Layer 1: API Controller - Defensive
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
private readonly IValidator<CreateOrderRequest> _validator;
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
// Defensive: validate DTO
if (request == null)
return BadRequest("Request body is required");
var validationResult = await _validator.ValidateAsync(request);
if (!validationResult.IsValid)
return BadRequest(validationResult.Errors);
try
{
var order = await _orderService.CreateOrderAsync(request);
return Ok(order);
}
catch (RiskLimitExceededException ex)
{
return BadRequest(new { error = ex.Message, code = "RISK_LIMIT" });
}
catch (InsufficientFundsException ex)
{
return BadRequest(new { error = ex.Message, code = "INSUFFICIENT_FUNDS" });
}
catch (ArgumentException ex)
{
return BadRequest(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error creating order");
return StatusCode(500, "An unexpected error occurred");
}
}
}
// Layer 2: FluentValidation - Defensive
public class CreateOrderRequestValidator : AbstractValidator<CreateOrderRequest>
{
public CreateOrderRequestValidator()
{
RuleFor(x => x.Symbol)
.NotEmpty().WithMessage("Symbol is required")
.MaximumLength(20).WithMessage("Symbol too long");
RuleFor(x => x.Quantity)
.GreaterThan(0).WithMessage("Quantity must be positive")
.LessThanOrEqualTo(1_000_000).WithMessage("Quantity too large");
RuleFor(x => x.Price)
.GreaterThanOrEqualTo(0).WithMessage("Price cannot be negative")
.LessThanOrEqualTo(1_000_000).WithMessage("Price too large");
RuleFor(x => x.AccountId)
.NotEmpty().WithMessage("Account ID is required");
}
}
// Layer 3: Application Service - Mixed
public class OrderService : IOrderService
{
public async Task<OrderDto> CreateOrderAsync(CreateOrderRequest request)
{
// Defensive: check account exists
var account = await _accountRepository.GetByIdAsync(request.AccountId);
if (account == null)
throw new NotFoundException($"Account {request.AccountId} not found");
// Fail-fast: create domain entity (enforces invariants)
var order = new Order(
request.Symbol,
request.Quantity,
request.Price,
request.Side
);
// Fail-fast: business rules
_riskValidator.ValidateOrder(order, account); // Throws on violation
_marginValidator.ValidateMargin(order, account); // Throws on violation
// Defensive: external system integration
var reservationResult = await TryReserveFundsAsync(account, order);
if (!reservationResult.Success)
{
throw new InsufficientFundsException(
$"Failed to reserve funds: {reservationResult.Reason}");
}
await _orderRepository.AddAsync(order);
await _unitOfWork.CommitAsync();
return _mapper.Map<OrderDto>(order);
}
private async Task<ReservationResult> TryReserveFundsAsync(Account account, Order order)
{
try
{
return await _fundingService.ReserveFundsAsync(account.Id, order.TotalValue);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to reserve funds for order");
return ReservationResult.Failure("Service unavailable");
}
}
}
// Layer 4: Domain Entity - Fail-Fast (shown above)
---
Advanced Scenarios
Q: Implement a circuit breaker pattern that uses defensive programming for external services but fail-fast for internal state.
A: Create a circuit breaker that protects against external failures defensively while enforcing internal state transitions with fail-fast.
public class CircuitBreaker
{
private CircuitState _state = CircuitState.Closed;
private int _failureCount;
private DateTime _lastFailureTime;
private readonly int _failureThreshold;
private readonly TimeSpan _openDuration;
private readonly SemaphoreSlim _lock = new(1, 1);
public CircuitBreaker(int failureThreshold, TimeSpan openDuration)
{
// Fail-fast: validate constructor parameters
if (failureThreshold <= 0)
throw new ArgumentException("Failure threshold must be positive", nameof(failureThreshold));
if (openDuration <= TimeSpan.Zero)
throw new ArgumentException("Open duration must be positive", nameof(openDuration));
_failureThreshold = failureThreshold;
_openDuration = openDuration;
}
public async Task<T> ExecuteAsync<T>(Func<Task<T>> operation)
{
// Fail-fast: null check
if (operation == null)
throw new ArgumentNullException(nameof(operation));
await _lock.WaitAsync();
try
{
// Check if we should transition from Open to HalfOpen
if (_state == CircuitState.Open &&
DateTime.UtcNow - _lastFailureTime >= _openDuration)
{
_state = CircuitState.HalfOpen;
}
// Fail-fast: enforce circuit state
if (_state == CircuitState.Open)
{
throw new CircuitBreakerOpenException(
$"Circuit breaker is open. Will retry after {_openDuration}");
}
}
finally
{
_lock.Release();
}
// Defensive: try operation and handle failures gracefully
try
{
var result = await operation();
// Success - reset if in HalfOpen
if (_state == CircuitState.HalfOpen)
{
await ResetAsync();
}
return result;
}
catch (Exception ex) when (!(ex is CircuitBreakerOpenException))
{
// Defensive: record failure and decide state transition
await RecordFailureAsync(ex);
throw; // Re-throw original exception
}
}
private async Task RecordFailureAsync(Exception ex)
{
await _lock.WaitAsync();
try
{
_failureCount++;
_lastFailureTime = DateTime.UtcNow;
if (_state == CircuitState.HalfOpen)
{
// Fail immediately on failure in HalfOpen state
_state = CircuitState.Open;
}
else if (_failureCount >= _failureThreshold)
{
_state = CircuitState.Open;
}
}
finally
{
_lock.Release();
}
}
private async Task ResetAsync()
{
await _lock.WaitAsync();
try
{
_failureCount = 0;
_state = CircuitState.Closed;
}
finally
{
_lock.Release();
}
}
}
public enum CircuitState { Closed, Open, HalfOpen }
Q: Design a price validation system that combines defensive parsing with fail-fast business rule enforcement.
A: Implement defensive parsing for external data and fail-fast validation for business rules.
public class PriceValidationService
{
public ValidatedPrice ValidateAndParsePrice(string symbol, string priceData)
{
// Fail-fast: validate inputs
if (string.IsNullOrWhiteSpace(symbol))
throw new ArgumentException("Symbol is required", nameof(symbol));
// Defensive: parse external data
if (string.IsNullOrWhiteSpace(priceData))
{
_logger.LogWarning("Empty price data for {Symbol}", symbol);
return ValidatedPrice.Invalid("Price data is empty");
}
if (!decimal.TryParse(priceData, NumberStyles.Any, CultureInfo.InvariantCulture, out var price))
{
_logger.LogWarning("Invalid price format for {Symbol}: {PriceData}", symbol, priceData);
return ValidatedPrice.Invalid($"Invalid price format: {priceData}");
}
// Fail-fast: enforce business rules
if (price < 0)
throw new InvalidPriceException($"Price cannot be negative: {price}");
if (price == 0)
throw new InvalidPriceException("Price cannot be zero");
if (price > 1_000_000)
throw new InvalidPriceException($"Price exceeds maximum allowed: {price}");
// Defensive: check for suspicious prices (but don't fail)
var previousPrice = _priceHistory.GetLastPrice(symbol);
if (previousPrice.HasValue)
{
var change = Math.Abs((price - previousPrice.Value) / previousPrice.Value);
if (change > 0.5m) // 50% change
{
_logger.LogWarning(
"Suspicious price change for {Symbol}: {PreviousPrice} -> {NewPrice} ({ChangePercent:P})",
symbol, previousPrice, price, change);
// Flag for review but allow it
return ValidatedPrice.Valid(price, isSuspicious: true);
}
}
return ValidatedPrice.Valid(price, isSuspicious: false);
}
}
public class ValidatedPrice
{
public bool IsValid { get; }
public decimal? Value { get; }
public string ErrorMessage { get; }
public bool IsSuspicious { get; }
private ValidatedPrice(bool isValid, decimal? value, string errorMessage, bool isSuspicious)
{
IsValid = isValid;
Value = value;
ErrorMessage = errorMessage;
IsSuspicious = isSuspicious;
}
public static ValidatedPrice Valid(decimal value, bool isSuspicious) =>
new(true, value, null, isSuspicious);
public static ValidatedPrice Invalid(string errorMessage) =>
new(false, null, errorMessage, false);
}
Q: Implement defensive retry logic with fail-fast on non-retryable errors.
A: Create a retry mechanism that defensively handles transient failures but fails fast on permanent errors.
public class ResilientHttpClient
{
private readonly HttpClient _httpClient;
private readonly ILogger _logger;
public async Task<T> GetWithRetryAsync<T>(
string url,
int maxRetries = 3,
CancellationToken cancellationToken = default)
{
// Fail-fast: validate parameters
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentException("URL is required", nameof(url));
if (maxRetries < 0)
throw new ArgumentException("Max retries cannot be negative", nameof(maxRetries));
Exception lastException = null;
for (int attempt = 0; attempt <= maxRetries; attempt++)
{
try
{
var response = await _httpClient.GetAsync(url, cancellationToken);
// Fail-fast: 4xx errors are not retryable (client errors)
if ((int)response.StatusCode >= 400 && (int)response.StatusCode < 500)
{
var content = await response.Content.ReadAsStringAsync();
throw new HttpRequestException(
$"Client error {response.StatusCode}: {content}. This is not retryable.");
}
// Defensive: 5xx errors are retryable (server errors)
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning(
"Request failed with {StatusCode} on attempt {Attempt}/{MaxRetries}",
response.StatusCode, attempt + 1, maxRetries + 1);
if (attempt < maxRetries)
{
await DelayWithJitterAsync(attempt);
continue;
}
throw new HttpRequestException($"Request failed after {maxRetries + 1} attempts");
}
return await response.Content.ReadFromJsonAsync<T>(cancellationToken);
}
catch (HttpRequestException) when ((int?)null >= 400 && (int?)null < 500)
{
// Re-throw client errors immediately (fail-fast)
throw;
}
catch (OperationCanceledException)
{
// Re-throw cancellation immediately (fail-fast)
throw;
}
catch (Exception ex)
{
// Defensive: log and retry on transient errors
lastException = ex;
_logger.LogWarning(
ex,
"Transient error on attempt {Attempt}/{MaxRetries}",
attempt + 1, maxRetries + 1);
if (attempt < maxRetries)
{
await DelayWithJitterAsync(attempt);
}
}
}
throw new HttpRequestException(
$"Request failed after {maxRetries + 1} attempts", lastException);
}
private async Task DelayWithJitterAsync(int attempt)
{
var baseDelay = TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt));
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 100));
await Task.Delay(baseDelay + jitter);
}
}
---
Real-World Trading Scenarios
Q: Implement order validation with defensive checks for external data and fail-fast for business rules.
A: Create a comprehensive order validator for a trading system.
public class OrderValidator
{
private readonly IAccountRepository _accountRepository;
private readonly IMarketDataService _marketDataService;
private readonly IRiskLimitService _riskLimitService;
private readonly ILogger _logger;
public async Task<ValidationResult> ValidateOrderAsync(CreateOrderRequest request)
{
// Fail-fast: null check
if (request == null)
throw new ArgumentNullException(nameof(request));
var errors = new List<string>();
// Defensive: validate symbol format
if (string.IsNullOrWhiteSpace(request.Symbol))
{
errors.Add("Symbol is required");
}
else if (request.Symbol.Length > 20)
{
errors.Add("Symbol too long (max 20 characters)");
}
// Defensive: validate quantity
if (request.Quantity <= 0)
{
errors.Add("Quantity must be positive");
}
else if (request.Quantity > 10_000_000)
{
errors.Add("Quantity exceeds maximum allowed");
}
// Defensive: validate price
if (request.Price < 0)
{
errors.Add("Price cannot be negative");
}
else if (request.Price > 1_000_000)
{
errors.Add("Price exceeds maximum allowed");
}
// Return early if basic validation failed
if (errors.Any())
{
return ValidationResult.Failure(errors);
}
// Defensive: check account exists
var account = await _accountRepository.GetByIdAsync(request.AccountId);
if (account == null)
{
errors.Add($"Account {request.AccountId} not found");
return ValidationResult.Failure(errors);
}
// Fail-fast: account must be active
if (!account.IsActive)
{
throw new InvalidOperationException($"Account {request.AccountId} is not active");
}
// Defensive: check if symbol is tradeable
var marketData = await _marketDataService.GetMarketDataAsync(request.Symbol);
if (marketData == null)
{
_logger.LogWarning("Market data not available for {Symbol}", request.Symbol);
errors.Add($"Market data not available for {request.Symbol}");
}
else if (!marketData.IsTradeable)
{
errors.Add($"{request.Symbol} is not currently tradeable");
}
else
{
// Defensive: check price deviation
var deviation = Math.Abs(request.Price - marketData.LastPrice) / marketData.LastPrice;
if (deviation > 0.1m) // 10% deviation
{
_logger.LogWarning(
"Large price deviation for {Symbol}: requested {RequestPrice}, market {MarketPrice}",
request.Symbol, request.Price, marketData.LastPrice);
errors.Add($"Price deviates significantly from market price ({deviation:P})");
}
}
// Fail-fast: check risk limits (business rules)
try
{
var orderValue = request.Quantity * request.Price;
_riskLimitService.ValidateOrderValue(account, orderValue);
_riskLimitService.ValidatePositionLimit(account, request.Symbol, request.Quantity);
_riskLimitService.ValidateMarginRequirement(account, orderValue);
}
catch (RiskLimitException ex)
{
// Convert business rule violations to validation errors
errors.Add(ex.Message);
}
return errors.Any()
? ValidationResult.Failure(errors)
: ValidationResult.Success();
}
}
public class ValidationResult
{
public bool IsValid { get; }
public IReadOnlyList<string> Errors { get; }
private ValidationResult(bool isValid, IReadOnlyList<string> errors)
{
IsValid = isValid;
Errors = errors ?? new List<string>();
}
public static ValidationResult Success() =>
new(true, Array.Empty<string>());
public static ValidationResult Failure(List<string> errors) =>
new(false, errors);
}
---
Total Exercises: 15+ comprehensive scenarios
Practice implementing the right balance between defensive programming and fail-fast for different layers of your application!